{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deep Learning model to detect suspicious DNS TXT records "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook uses a pre-trained deep learning model to predict whether a DNS TXT record is suspicious or not."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While DNS TXT records initially were supposed to only hold descriptive text, its use and format have been subject to changes and debates over the years. This makes TXT records ideal candidates for malicious and unintended practices which include adding large records to create more efficient DNS amplification attacks, or creating a command and control channel for malware. \n",
    "\n",
    "The model is trained independently and is then made available for download. The DNS TXT records are categorized into commonly identified types like email, verification, http using regular expressions https://www.tide-project.nl/blog/wtmc2020/. The TXT records that do not match regular expressions for well known types are labeled as 1 for \"unknown/suspicious\" and otherwise 0 for \"not suspicious\". The deep learning model we have developed uses DNX TXT responses to analyze patters of character sequences to predict of a DNS TXT is suspicious or not. Higher the pred_is_unknown_proba, the more likely is the DNS TXT record is suspicious. The threshold for flagging a domain as suspicious is set at 0.5."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 0 - import libraries\n",
    "At stage 0 we define all imports necessary to run our subsequent code depending on various libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "deletable": false,
    "name": "mltkc_import"
   },
   "outputs": [],
   "source": [
    "# this definition exposes all python module imports that should be available in all subsequent commands\n",
    "import numpy as np \n",
    "import pandas as pd \n",
    "import os\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torch.utils.data.sampler import SubsetRandomSampler\n",
    "from torch.utils.data import Dataset\n",
    "from torch.utils.data import DataLoader\n",
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "from torch.autograd import Variable\n",
    "from torch.optim import lr_scheduler\n",
    "from collections import Counter,OrderedDict\n",
    "import pickle\n",
    "\n",
    "# global constants\n",
    "MODEL_DIRECTORY = \"/srv/app/model/data/detect_suspicious_dns_txt_records_using_pretrained_model_in_dsdl/\"\n",
    "vocab_size = 10002\n",
    "embedding_dim = 64\n",
    "hidden_size = 64\n",
    "fc_hidden_size = 64\n",
    "num_output_nodes = 1\n",
    "\n",
    "dropout = 0.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "numpy version: 1.19.2\n",
      "pandas version: 1.1.3\n",
      "torch version: 1.11.0\n"
     ]
    }
   ],
   "source": [
    "# THIS CELL IS NOT EXPORTED - free notebook cell for testing or development purposes\n",
    "print(\"numpy version: \" + np.__version__)\n",
    "print(\"pandas version: \" + pd.__version__)\n",
    "print(\"torch version: \" + torch.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 1 - get a data sample from Splunk\n",
    "There are currently 2 ways to retrieve data from Splunk: Option 1 is to interactively pull data from Splunk into the DLTK Jupyter Lab environment. This is useful when the Splunk REST API is accessible from the Jupyter environment and a valid Splunk auth token is defined in the DLTK app. This option has advantages to quickly experiment with different Splunk SPL queries and further interactively work with the search results in Jupyter.\n",
    "\n",
    "### Option 1 - pull data from Splunk"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import libs.SplunkSearch as SplunkSearch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "8b33153fe3ca43febb2257b1791064fa",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(HBox(children=(Textarea(value='| makeresults count=10 \\n| streamstats c as i \\n| eval s = i%3 \\…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "search = SplunkSearch.SplunkSearch()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>_time</th>\n",
       "      <th>feature_0</th>\n",
       "      <th>feature_1</th>\n",
       "      <th>feature_2</th>\n",
       "      <th>i</th>\n",
       "      <th>s</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.8460328145883977</td>\n",
       "      <td>0.32027013367041945</td>\n",
       "      <td>0.8921294794417918</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.3228773274458945</td>\n",
       "      <td>0.9270845879800618</td>\n",
       "      <td>0.6180154159665108</td>\n",
       "      <td>2</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.9652930609881878</td>\n",
       "      <td>0.6127289566211402</td>\n",
       "      <td>0.6959671885706484</td>\n",
       "      <td>3</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.9985754322260618</td>\n",
       "      <td>0.7212827517651021</td>\n",
       "      <td>0.6265083532780409</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.4984334879554808</td>\n",
       "      <td>0.4883861029520631</td>\n",
       "      <td>0.8144497619941831</td>\n",
       "      <td>5</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.9335020575672388</td>\n",
       "      <td>0.7346397545188665</td>\n",
       "      <td>0.03047862323001027</td>\n",
       "      <td>6</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.8374560391530395</td>\n",
       "      <td>0.9159072837792337</td>\n",
       "      <td>0.01808864250779152</td>\n",
       "      <td>7</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.8355274042114615</td>\n",
       "      <td>0.3842416317202151</td>\n",
       "      <td>0.1918810959905386</td>\n",
       "      <td>8</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.35425164457410574</td>\n",
       "      <td>0.11845849128440022</td>\n",
       "      <td>0.01499031251296401</td>\n",
       "      <td>9</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>2022-05-18T10:30:54.000+02:00</td>\n",
       "      <td>0.38959994819015265</td>\n",
       "      <td>0.7069222498685122</td>\n",
       "      <td>0.930438622366637</td>\n",
       "      <td>10</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           _time            feature_0            feature_1  \\\n",
       "0  2022-05-18T10:30:54.000+02:00   0.8460328145883977  0.32027013367041945   \n",
       "1  2022-05-18T10:30:54.000+02:00   0.3228773274458945   0.9270845879800618   \n",
       "2  2022-05-18T10:30:54.000+02:00   0.9652930609881878   0.6127289566211402   \n",
       "3  2022-05-18T10:30:54.000+02:00   0.9985754322260618   0.7212827517651021   \n",
       "4  2022-05-18T10:30:54.000+02:00   0.4984334879554808   0.4883861029520631   \n",
       "5  2022-05-18T10:30:54.000+02:00   0.9335020575672388   0.7346397545188665   \n",
       "6  2022-05-18T10:30:54.000+02:00   0.8374560391530395   0.9159072837792337   \n",
       "7  2022-05-18T10:30:54.000+02:00   0.8355274042114615   0.3842416317202151   \n",
       "8  2022-05-18T10:30:54.000+02:00  0.35425164457410574  0.11845849128440022   \n",
       "9  2022-05-18T10:30:54.000+02:00  0.38959994819015265   0.7069222498685122   \n",
       "\n",
       "             feature_2   i  s  \n",
       "0   0.8921294794417918   1  1  \n",
       "1   0.6180154159665108   2  2  \n",
       "2   0.6959671885706484   3  0  \n",
       "3   0.6265083532780409   4  1  \n",
       "4   0.8144497619941831   5  2  \n",
       "5  0.03047862323001027   6  0  \n",
       "6  0.01808864250779152   7  1  \n",
       "7   0.1918810959905386   8  2  \n",
       "8  0.01499031251296401   9  0  \n",
       "9    0.930438622366637  10  1  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = search.as_df()\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Option 2 - push data from Splunk\n",
    "In Splunk run a search to pipe a dataset into your notebook environment. You utilize the `mode=stage` flag in the in the `| fit` command to do this. The search results are accessible then as csv file with the same model name that is defined in the `into app:<modelname>` part of the fit statement. Additionally, meta data is retrieved and accessible as json file. In the same way you can further work with the meta data object as it is exposed in the fit and apply function definitions below in stage 3 and 4."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| makeresults count=10<br>\n",
    "| streamstats c as i<br>\n",
    "| fit MLTKContainer mode=stage algo=barebone_template i into app:barebone_template"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After you run this search your data set sample is available as a csv inside the container to develop your model. The name is taken from the into keyword (\"barebone_model\" in the example above) or set to \"default\" if no into keyword is present. This step is intended to work with a subset of your data to create your custom model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "name": "mltkc_stage"
   },
   "outputs": [],
   "source": [
    "# this cell is not executed from MLTK and should only be used for staging data into the notebook environment\n",
    "def stage(name):\n",
    "    with open(\"data/\"+name+\".csv\", 'r') as f:\n",
    "        df = pd.read_csv(f)\n",
    "    with open(\"data/\"+name+\".json\", 'r') as f:\n",
    "        param = json.load(f)\n",
    "    return df, param"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# THIS CELL IS NOT EXPORTED - free notebook cell for testing or development purposes\n",
    "df, param = stage(\"barebone_template\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 2 - create and initialize a model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "deletable": false,
    "name": "mltkc_init"
   },
   "outputs": [],
   "source": [
    "# initialize your model\n",
    "# available inputs: data and parameters\n",
    "# returns the model object which will be used as a reference to call fit, apply and summary subsequently\n",
    "\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  \n",
    "v = CountVectorizer(analyzer='char_wb', ngram_range=(2, 4), lowercase=True)\n",
    "analyzer = v.build_analyzer()\n",
    "def tokenize(s):\n",
    "    tokens = analyzer(s)\n",
    "    \n",
    "    try:\n",
    "        tokens.remove(' ')\n",
    "    except ValueError:\n",
    "        pass\n",
    "    tokens = list(map(str.strip, tokens))\n",
    "    return tokens\n",
    "\n",
    "class DataFrameDataset(Dataset):\n",
    "\n",
    "    def __init__(self, df):\n",
    "        self.texts = []\n",
    "        self.labels = []\n",
    "        for i, row in df.iterrows():\n",
    "            label = row.is_unknown\n",
    "            text = row.text\n",
    "            self.texts.append(text)\n",
    "            self.labels.append(label)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.labels)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return self.texts[idx],self.labels[idx]\n",
    "\n",
    "\n",
    "class DNSTxtClassifier(nn.Module):\n",
    "        def __init__(self, vocab_size, embed_dim, hidden_size, fc_hidden_size,dropout=0.5,layers=1):\n",
    "            super().__init__()\n",
    "            self.embedding = nn.Embedding(vocab_size,embed_dim,padding_idx=0)\n",
    "            self.lstm = nn.LSTM(embed_dim, hidden_size,batch_first=True,dropout=dropout, num_layers=1)\n",
    "            self.fc2 = nn.Linear(hidden_size, 1)\n",
    "            self.sigmoid = nn.Sigmoid()\n",
    "#lengths.to(device)\n",
    "        def forward(self, text, lengths):\n",
    "            out = self.embedding(text)\n",
    "            out = nn.utils.rnn.pack_padded_sequence(out,lengths.detach().numpy() , enforce_sorted=False, batch_first=True )\n",
    "            out, (hidden, cell) = self.lstm(out)\n",
    "            out = hidden[-1, :, :]\n",
    "            out = self.fc2(out)\n",
    "            out = self.sigmoid(out)\n",
    "            return out\n",
    "        \n",
    "\n",
    "\n",
    "def init(df,param):\n",
    "    model = DNSTxtClassifier(vocab_size, embedding_dim, hidden_size,fc_hidden_size,dropout = dropout)\n",
    "    model.load_state_dict(torch.load(MODEL_DIRECTORY+'/detect_suspicious_dns_txt_records_using_pretrained_model_in_dsdl.pt',map_location=torch.device('cpu')))\n",
    "    model = model.to(device)\n",
    "    model.eval()\n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# THIS CELL IS NOT EXPORTED - free notebook cell for testing or development purposes\n",
    "model = init(df,param)\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 3 - fit the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "name": "mltkc_fit"
   },
   "outputs": [],
   "source": [
    "# train your model\n",
    "# returns a fit info json object and may modify the model object\n",
    "def fit(model,df,param):\n",
    "    # model.fit()\n",
    "    info = {\"message\": \"model trained\"}\n",
    "    return info"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# THIS CELL IS NOT EXPORTED - free notebook cell for testing or development purposes\n",
    "print(fit(model,df,param))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 4 - apply the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "name": "mltkc_apply"
   },
   "outputs": [],
   "source": [
    "# apply your model\n",
    "# returns the calculated results\n",
    "# apply your model\n",
    "# returns the calculated results\n",
    "vocab = pickle.load(open(MODEL_DIRECTORY+\"vocab.pkl\", 'rb'))\n",
    "\n",
    "def remove_spaces_key_value(text):\n",
    "     return text.replace(' = ','=').replace(\" =\",\"=\").replace('= ','=').replace(' - ','-').replace(\" -\",\"-\").replace('- ','-').replace(' : ',\":\").replace(': ' ,\":\").replace(' :',':')\n",
    "\n",
    "def get_token_index(text):\n",
    "    idxs = []\n",
    "    tokens = tokenize(text)\n",
    "    for token in tokens:\n",
    "        if token not in vocab:\n",
    "            idxs.append(vocab['<unk>'])\n",
    "        else:\n",
    "            idxs.append(vocab[token])\n",
    "    return idxs\n",
    "\n",
    "def preprocess_text(text):\n",
    "    text = remove_spaces_key_value(text)\n",
    "    text = text.replace('\"','').replace(\"'\",\"\").lower()\n",
    "    return text\n",
    "\n",
    "\n",
    "def apply(model,df,param):\n",
    "    is_unknown_probability_score = []\n",
    "    is_unknown = []\n",
    "    for idx, row in df.iterrows():\n",
    "        text = row['text']\n",
    "        text = preprocess_text(text)\n",
    "        indexed_text = get_token_index(text)\n",
    "        length = [len(indexed_text)]\n",
    "        token_tensor = torch.LongTensor(indexed_text).to(device)             \n",
    "        token_tensor = token_tensor.unsqueeze(1).T                            \n",
    "        length_tensor = torch.LongTensor(length) \n",
    "        pred_proba = model(token_tensor, length_tensor)[:, 0].item()\n",
    "        pred  = int(pred_proba >= 0.5)\n",
    "        is_unknown_probability_score.append(pred_proba)\n",
    "        is_unknown.append(pred)\n",
    "    output = pd.DataFrame()\n",
    "    output['predicted_is_unknown'] = is_unknown_probability_score\n",
    "    output['pred_is_unknown'] = is_unknown\n",
    "    return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# THIS CELL IS NOT EXPORTED - free notebook cell for testing or development purposes\n",
    "print(apply(model,df,param))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 5 - save the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "name": "mltkc_save"
   },
   "outputs": [],
   "source": [
    "# save model to name in expected convention \"<algo_name>_<model_name>\"\n",
    "def save(model,name):\n",
    "    with open(MODEL_DIRECTORY + name + \".json\", 'w') as file:\n",
    "        json.dump(model, file)\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 6 - load the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "name": "mltkc_load"
   },
   "outputs": [],
   "source": [
    "# load model from name in expected convention \"<algo_name>_<model_name>\"\n",
    "def load(name):\n",
    "    model = DNSTxtClassifier(vocab_size, embedding_dim, hidden_size,fc_hidden_size,dropout = dropout)\n",
    "    model.load_state_dict(torch.load(MODEL_DIRECTORY+'/detect_suspicious_dns_txt_records_using_pretrained_model_in_dsdl.pt',map_location=torch.device('cpu')))\n",
    "    model = model.to(device)\n",
    "    model.eval()\n",
    "    return model "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 7 - provide a summary of the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "name": "mltkc_summary"
   },
   "outputs": [],
   "source": [
    "# return a model summary\n",
    "def summary(model=None):\n",
    "    returns = {\"version\": {\"numpy\": np.__version__, \"pandas\": pd.__version__} }\n",
    "    return returns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After implementing your fit, apply, save and load you can train your model:<br>\n",
    "| makeresults count=10<br>\n",
    "| streamstats c as i<br>\n",
    "| eval s = i%3<br>\n",
    "| eval feature_{s}=0<br>\n",
    "| foreach feature_* [eval &lt;&lt;FIELD&gt;&gt;=random()/pow(2,31)]<br>\n",
    "| fit MLTKContainer algo=barebone s from feature_* into app:barebone_model<br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or apply your model:<br>\n",
    "| makeresults count=10<br>\n",
    "| streamstats c as i<br>\n",
    "| eval s = i%3<br>\n",
    "| eval feature_{s}=0<br>\n",
    "| foreach feature_* [eval &lt;&lt;FIELD&gt;&gt;=random()/pow(2,31)]<br>\n",
    "| apply barebone_model as the_meaning_of_life"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Send data back to Splunk HEC\n",
    "When you configured the Splunk HEC Settings in the DLTK app you can easily send back data to an index with [Splunk's HTTP Event Collector (HEC)](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector). Read more about data formats and options in the [documentation](https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector#Event_metadata).\n",
    "\n",
    "### Use cases\n",
    "- you want to offload longer running, possibly distributed computations that need to deliver results asynchroneously back into Splunk. \n",
    "- you might not want to present results back into the search pipeline after your `| fit` or `| apply` command. \n",
    "- you can easily utilize this approach for any logging purposes or other profiling tasks in your ML code so you can actively monitor and analyze your processes.\n",
    "\n",
    "### Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import libs.SplunkHEC as SplunkHEC\n",
    "hec = SplunkHEC.SplunkHEC()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# example to send 10 hello world events\n",
    "response = hec.send_hello_world(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"HEC endpoint %s \\nreturned with status code %s \\nand response message: %s\" % (response.url, response.status_code, response.text))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# example to send a JSON object, e.g. to log some data\n",
    "from datetime import datetime\n",
    "response = hec.send({'event': {'message': 'operation done', 'log_level': 'INFO' }, 'time': datetime.now().timestamp()})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"HEC endpoint %s \\nreturned with status code %s \\nand response message: %s\" % (response.url, response.status_code, response.text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## End of Stages\n",
    "All subsequent cells are not tagged and can be used for further freeform code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
